﻿using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using X.Core.Interfaces;

namespace X.Core.Bases
{
    public abstract class SaveModelBase : ModelBase, ISaveModel
    {
        public SaveModelBase() : base()
        {
            CreatedTime = DateTime.Now;
            ModifiedTime = DateTime.Now;
            VersionT = 1;
            Version = "1.0.0";
            Author = "Unknown";
        }
        public virtual string[] GetDefaultIgnoreData()
        {
            return DefaultData;
        }
        public virtual (int, string) Save(string path)
        {
            try
            {
                SavedTime = DateTime.Now;
                IsChanged = false;
                string str = Serialize();
                SaveStrFile(path, str);
                return (0, null);
            }
            catch (Exception ex)
            {
                return (-1, ex.Message);
            }
        }

        public virtual (int, string) Load<T>(string path) where T : SaveModelBase
        {
            try
            {
                if (File.Exists(path) == false) return (-1, $"{path} is not exist");
                var str = File.ReadAllText(path);
                var model = Deserialize<T>(str);
                return Fill(model, true,GetDefaultIgnoreData());
            }
            catch (Exception ex)
            {
                return (-1, ex.Message);
            }
        }

        public virtual string Serialize(JObject param = null)
        {
            return JsonConvert.SerializeObject(this, Formatting.Indented);
        }
        public static T Deserialize<T>(string str) where T : SaveModelBase
        {
            return JsonConvert.DeserializeObject<T>(str);
        }
        public virtual void SaveStrFile(string filePath, string contents, bool isCheckFile = true)
        {
            string file = $"{filePath}{(isCheckFile ? ".tmp" : string.Empty)}";
            var maxRetry = 3;
            bool isOk = false;
            for (int i = 0; i < maxRetry; i++)
            {
                File.WriteAllText(file, contents, Encoding.UTF8);
                if (isCheckFile)
                {
                    var str = File.ReadAllText(file);
                    var chkResult = CheckStr(contents, str);
                    if (chkResult)
                    {
                        if (File.Exists(filePath))
                        {
                            File.Delete(filePath);
                        }
                        File.Move(file, filePath);
                        isOk = true;
                    }
                }
                else
                {
                    isOk = true;
                }
                if (isOk)
                {
                    break;
                }
            }
            if (!isOk)
            {
                throw new IOException($"Failed to save file after {maxRetry} attempts: {filePath}");
            }
        }

        public virtual bool CheckStr(string str1, string str2)
        {
            MD5CryptoServiceProvider md = new MD5CryptoServiceProvider();
            var md1 = md.ComputeHash(Encoding.UTF8.GetBytes(str1));
            var md2 = md.ComputeHash(Encoding.UTF8.GetBytes(str2));
            if (md1 == null || md2 == null) return false;
            if (md1.Length != md2.Length) return false;
            for (int i = 0; i < md1.Length; i++)
            {
                if (md1[i] != md2[i]) return false;
            }
            md.Dispose();
            return true;
        }

        #region 'Fields'
        DateTime _loadedTime;
        DateTime _savedTime;
        DateTime _createdTime;
        DateTime _modifiedTime;
        string _author;
        int _versionT;
        string _version;
        #endregion

        #region 'Properties'
        public static string[] DefaultData = new string[]
        {
            "RelayCommand",
            "ObservableCollection`1",
            "Dictionary`2",
            "List`1",
            "IsChanged"
        };
        public DateTime LoadedTime { get => _loadedTime; set => SetProperty(ref _loadedTime, value); }
        public DateTime SavedTime { get => _savedTime; set => SetProperty(ref _savedTime, value); }
        public DateTime CreatedTime { get => _createdTime; set => SetProperty(ref _createdTime, value); }
        public DateTime ModifiedTime { get => _modifiedTime; set => SetProperty(ref _modifiedTime, value); }
        public string Author { get => _author; set => SetProperty(ref _author, value); }
        public int VersionT { get => _versionT; set => SetProperty(ref _versionT, value); }
        public string Version { get => _version; set => SetProperty(ref _version, value); }
        #endregion
    }
}
